﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using System.Reflection;

namespace OfficeClip.OpenSource.Logger
{
    public class FileLog : ILog
    {
        public string FilePath { get; private set; }

        private bool isFileLogValid;

        private readonly LogConfig logConfig;

        private string DefaultLogFolder
        {
            get
            {
                return string.Format(
                                     "{0}\\log",
                                     LogConstants.GetSetupDir());
            }
        }

        public string LogFolder
        {
            get
            {
                return logConfig.FileLogSource.Folder;
            }
            set
            {
                logConfig.FileLogSource.Folder = value;
                //CreateLogFolderIfNotFound();
                //CreateIndexFile();
                //CreateLogFile();
            }
        }

        #region ILog

        public bool IsEnabled
        {
            get
            {
                return logConfig.FileLogSource.Supress == 0;
            }
        }

        public void Initialize()
        {
            isFileLogValid = true;

            try
            {
                CreateLogFolderIfNotFound();
                CreateIndexHtmlFile();
                CreateIndexFile();
                CreateLogFile();
                Cleanup();
            }
            catch
            {
                isFileLogValid = false;
            }
        }

        private XElement[] SplitStackTrace(string stackInfo)
        {
            List<XElement> stackLine = new List<XElement>();
            using (StringReader reader = new StringReader(stackInfo))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    stackLine.Add(new XElement("stl", line.Trim()));
                }
            }
            return stackLine.ToArray();
        }



        public void WriteLog(
            LogState state,
            string stackInfo,
            string topic,
            string description,
            string category,
            string user,
            string custom)
        {
            if (!isFileLogValid)
            {
                return;
            }
            try
            {
                if (IsLogDayLimitReached() || IsLogSizeLimitReached())
                {
                    Thread.Sleep(2000);
                    CreateLogFile();
                }
                XDocument xDocument = XDocument.Load(FilePath);
                XElement root = xDocument.Element("logs");
                root.Add(
                         new XElement(
                             "log",
                             new XElement(
                                 "dt",
                                 DateTime.Now.ToString("MMM dd, yyyy")),
                             new XElement(
                                 "tm",
                                 DateTime.Now.ToString("hh:mm:ss tt")),
                            new XElement(
                                "st",
                                SplitStackTrace(stackInfo)
                                //stackInfo
                                ),
                             new XElement(
                                 "t",
                                 topic),
                             new XElement(
                                 "d",
                                 description),
                             new XElement(
                                 "s",
                                 state),
                             new XElement(
                                 "c",
                                 category),
                             new XElement(
                                 "u",
                                 user),
                             new XElement(
                                 "cu",
                                 custom)));
                xDocument.Save(FilePath);
                if (logConfig.FileLogSource.ShowStatistics == 1)
                {
                    UpdateIndex(state);
                }
            }
            catch
            {

            }
        }

        public void Cleanup()
        {
            int purgeDays = logConfig.PurgeDays;
            foreach (string path in Directory.GetFiles(
                                                       LogFolder,
                                                       "*.xml"))
            {
                if (Path.GetFileName(path) == "index.xml")
                {
                    continue;
                }
                bool isDeleteFile = File.GetCreationTime(path).Date < DateTime.Now.AddDays(-1 * purgeDays).Date;
                if (isDeleteFile)
                {
                    File.Delete(path);
                    DeleteNodeFromIndex(Path.GetFileName(path));
                }
            }
        }

        #endregion //ILog

        internal FileLog(LogConfig _logConfig)
        {
            logConfig = _logConfig;
            if (string.IsNullOrWhiteSpace(logConfig.FileLogSource.Folder))
            {
                logConfig.FileLogSource.Folder = DefaultLogFolder;
            }
            if (logConfig.FileLogSource.CurrentLogDayLimit == 0)
            {
                logConfig.FileLogSource.CurrentLogDayLimit = 1;
            }
            if (logConfig.FileLogSource.CurrentLogSizeLimitKb == 0)
            {
                logConfig.FileLogSource.CurrentLogSizeLimitKb = 100;
            }
        }

        private bool IsLogDayLimitReached()
        {
            return (File.GetCreationTime(FilePath).Date.AddDays(logConfig.FileLogSource.CurrentLogDayLimit) <=
                    DateTime.Now.Date);
        }

        private bool IsLogSizeLimitReached()
        {
            FileInfo fileInfo = new FileInfo(FilePath);
            return (fileInfo.Length > (logConfig.FileLogSource.CurrentLogSizeLimitKb * 1024));
        }


        internal void CreateLogFolderIfNotFound()
        {
            if (!Directory.Exists(LogFolder))
            {
                Directory.CreateDirectory(LogFolder);
            }
            LogConstants.ProvideFullAccess(LogFolder);
        }

        private void DeleteNodeFromIndex(string fileName)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(IndexPath);
            XmlNode node = doc.SelectSingleNode("//name[text()='" + fileName + "']");
            node = node.ParentNode;
            node.ParentNode.RemoveChild(node);
            doc.Save(IndexPath);
        }

        private string CreateFilePath()
        {
            return string.Format(
                                    @"{0}\log.{1}.{2}.xml",
                                    LogFolder,
                                    DateTime.Now.ToString("yyyyMMdd"),
                                    DateTime.Now.ToString("HHmmss"));
        }

        private string IndexPath
        {
            get
            {
                return string.Format(
                                       @"{0}\index.xml",
                                       LogFolder);

            }
        }

        private string IndexHtmlPath
        {
            get
            {
                return string.Format(
                                       @"{0}\index.html",
                                       LogFolder);

            }
        }
        private XmlWriterSettings XmlWriterSettings
        {
            get
            {
                XmlWriterSettings xmlWriterSettings = new XmlWriterSettings
                                                      {
                                                          Indent = true,
                                                          NewLineOnAttributes = true
                                                      };
                return xmlWriterSettings;
            }
        }

        private void AddXsltHeaders(XmlWriter logWriter, string topNode)
        {
            const string processingInstructionText = "type=\"text/xsl\" href=\"#stylesheet\"";
            logWriter.WriteProcessingInstruction("xml-stylesheet", processingInstructionText);
            logWriter.WriteDocType(topNode, null, null, "\n<!ATTLIST xsl:stylesheet\n id ID #REQUIRED>\n");
        }

        private string GetEmbeddedResourceContent(string resourceFileName)
        {
            var assembly = Assembly.GetExecutingAssembly();
            string assemblyName = "OfficeClip.OpenSource.Logger." + resourceFileName;
            using (Stream stream = assembly.GetManifestResourceStream(assemblyName))
            {
                using (XmlTextReader reader = new XmlTextReader(stream))
                {
                    reader.MoveToContent();
                    return reader.ReadOuterXml();
                }
            }
        }

        private string GetFileContent(string fileName)
        {
            using (XmlTextReader reader = new XmlTextReader(fileName))
            {
                reader.MoveToContent();
                return reader.ReadOuterXml();
            }
        }

        private string GetIndexXsltContent()
        {
            string applicationPath = LogConstants.GetSetupDir() + "\\";
            return (string.IsNullOrWhiteSpace(logConfig.FileLogSource.IndexXslt)) ?
                GetEmbeddedResourceContent("index.xslt") :
                GetFileContent(applicationPath + logConfig.FileLogSource.IndexXslt);
        }

        private string GetLogXsltContent()
        {
            string applicationPath = LogConstants.GetSetupDir() + "\\";
            return (string.IsNullOrWhiteSpace(logConfig.FileLogSource.LogXslt)) ?
                GetEmbeddedResourceContent("log.xslt") :
                GetFileContent(applicationPath + logConfig.FileLogSource.LogXslt);
        }

        internal void CreateIndexHtmlFile()
        {
            if (!IsEnabled)
            {
                return;
            }
            if (!File.Exists(IndexHtmlPath))
            {
                using (StreamWriter streamWriter = File.CreateText(IndexHtmlPath))
                {
                    streamWriter.Write(
                        GetEmbeddedResourceContent("index.html"));
                }
            }
        }

        internal void CreateIndexFile()
        {
            if (!IsEnabled)
            {
                return;
            }
            if (!File.Exists(IndexPath))
            {
                using (XmlWriter logWriter = XmlWriter.Create(
                                                              IndexPath,
                                                              XmlWriterSettings))
                {
                    AddXsltHeaders(logWriter, "files");
                    logWriter.WriteStartElement("files");
                    string stylesheetString = GetIndexXsltContent();
                    logWriter.WriteRaw(stylesheetString);
                    logWriter.WriteEndElement();
                    logWriter.Flush();
                }
            }
        }

        internal void CreateLogFile()
        {
            FilePath = CreateFilePath();
            using (XmlWriter logWriter = XmlWriter.Create(FilePath, XmlWriterSettings))
            {
                AddXsltHeaders(logWriter, "logs");

                logWriter.WriteStartElement("logs");
                string stylesheetString = GetLogXsltContent();

                logWriter.WriteRaw(stylesheetString);
                logWriter.WriteEndElement();
                logWriter.Flush();
                WriteIndex(Path.GetFileName(FilePath), File.GetCreationTime(FilePath).ToString("G"));
            }


        }

        ~FileLog()
        {
        }

        public string GetLogStateString(LogState state)
        {
            return state.ToString();
        }

        private void UpdateIndex(LogState state)
        {
            try
            {
                XDocument xDocument = XDocument.Load(IndexPath);
                var query = from c in xDocument.Elements("files").Elements("file")
                            select c;
                foreach (XElement file in query)
                {
                    if (file.Element("name").Value == Path.GetFileName(FilePath))
                    {
                        file.Element("lines").Value =
                            string.IsNullOrEmpty(file.Element("lines").Value)
                                ? "1"
                                : (Convert.ToInt32(file.Element("lines").Value) + 1).ToString();
                        switch (state)
                        {
                            case LogState.FatalError:
                                file.Element("fatal").Value =
                                    string.IsNullOrEmpty(file.Element("fatal").Value)
                                        ? "1"
                                        : (Convert.ToInt32(file.Element("fatal").Value) + 1).ToString();
                                break;
                            case LogState.Error:
                                file.Element("error").Value =
                                    string.IsNullOrEmpty(file.Element("error").Value)
                                        ? "1"
                                        : (Convert.ToInt32(file.Element("error").Value) + 1).ToString();
                                break;
                            case LogState.Warning:
                                file.Element("warning").Value =
                                    string.IsNullOrEmpty(file.Element("warning").Value)
                                        ? "1"
                                        : (Convert.ToInt32(file.Element("warning").Value) + 1).ToString();
                                break;
                        }
                    }
                }
                xDocument.Save(IndexPath);
            }
            catch
            {

            }
        }

        internal void WriteIndex(
            string fileName,
            string displayName
            )
        {
            if (!isFileLogValid)
            {
                return;
            }
            try
            {
                XDocument xDocument = XDocument.Load(IndexPath);
                XElement root = xDocument.Element("files");
                root.Add(
                    new XElement(
                             "file",
                             new XElement(
                                 "name",
                                 fileName),
                             new XElement(
                                 "display",
                                 displayName),
                             new XElement(
                                 "lines",
                                 string.Empty),
                             new XElement(
                                 "fatal",
                                 string.Empty),
                             new XElement(
                                 "error",
                                 string.Empty),
                             new XElement(
                                 "warning",
                                 string.Empty)));
                xDocument.Save(IndexPath);
            }
            catch
            {

            }
        }

    }
}
